import collections

from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.proto.coin_order_gateway_pb2 import OrderEvent
from coin.proto.coin_order_enums_pb2 import (
    BUY_ORDER,
    BUY_OPEN_ORDER,
    BUY_CLOSE_ORDER,
    SELL_ORDER,
    SELL_OPEN_ORDER,
    SELL_CLOSE_ORDER,
)


def get_order_sign(order_side):
  sign = None
  if order_side in [BUY_ORDER, BUY_OPEN_ORDER, BUY_CLOSE_ORDER]:
    sign = 1
  elif order_side in [SELL_ORDER, SELL_OPEN_ORDER, SELL_CLOSE_ORDER]:
    sign = -1
  elif order_side == 0:
    sign = 0
  else:
    raise ValueError(order_side)
  return sign


TpqsTuple = collections.namedtuple('PqsTuple', ['timestamp', 'price', 'qty', 'sign'])


def first(s):
  '''Return the first element from an ordered collection
     or an arbitrary element from an unordered collection.
     Raise StopIteration if the collection is empty.
  '''
  return next(iter(s))


class KuonaOrderManager(object):
  def __init__(self, exchange, product):
    self.exchange = exchange
    self.order_tpqs = collections.OrderedDict()
    self.internal_to_external_map = {}
    self.self_order_book = collections.defaultdict(int)
    self.self_order_book_queue = []
    self.product = product
    self.product_holder = get_holder_from_product(self.product)

  def update_obs(self, external_order_id, diff_qty):
    if external_order_id not in self.order_tpqs:
      return
    price = self.order_tpqs[external_order_id].price

    self.order_tpqs[external_order_id] = self.order_tpqs[external_order_id]._replace(
        qty=(self.order_tpqs[external_order_id].qty + diff_qty))

    if self.order_tpqs[external_order_id].qty < 1e-8:
      del self.order_tpqs[external_order_id]

    self.self_order_book[price] += diff_qty
    if self.self_order_book[price] < 1e-8:
      del self.self_order_book[price]

  def on_event(self, event):
    timestamp = event.event_time
    price = self.product_holder.sanitize_price(event.order_price)
    qty = event.order_qty
    side = event.order_side
    event_type = event.type
    fill_price = event.fill_price
    fill_qty = event.fill_qty
    sign = get_order_sign(event.order_side)

    diff_qty = None
    if event.type == OrderEvent.ORDER_ACCEPTED:
      self.internal_to_external_map[event.internal_order_id] = event.external_order_id
      self.order_tpqs[event.external_order_id] = TpqsTuple(timestamp, price, 0, sign)
      diff_qty = qty
    elif event.type == OrderEvent.ORDER_FILLED:
      diff_qty = -fill_qty
    elif (event.type == OrderEvent.CANCEL_CONFIRMED and event.external_order_id in self.order_tpqs):
      diff_qty = -self.order_tpqs[event.external_order_id].qty

    if diff_qty is not None:
      self.update_obs(event.external_order_id, diff_qty)
      if self.exchange == 'Bitflyer':
        for iid in list(self.internal_to_external_map):
          eid = self.internal_to_external_map[iid]
          if eid not in self.order_tpqs:
            del self.internal_to_external_map[iid]
          elif self.order_tpqs[eid].timestamp + 1e9 < timestamp:
            self.update_obs(eid, -self.order_tpqs[eid].qty)
            del self.internal_to_external_map[iid]

      while (len(self.order_tpqs) > 20
             or (len(self.order_tpqs) > 0
                 and self.order_tpqs[first(self.order_tpqs)].timestamp + 60e9 < event.event_time)):
        self.update_obs(first(self.order_tpqs), -self.order_tpqs[first(self.order_tpqs)].qty)
      return True
    else:
      return False
